package com.lili.collection.core.processor;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.lili.collection.core.domain.CollectCommonConfig;
import com.lili.collection.core.domain.FetchDataInfo;
import com.lili.collection.core.domain.LinkedNode;
import com.lili.collection.core.fields.Field;
import com.lili.collection.core.runner.result.RecordInfo;
import com.lili.collection.core.utils.DateConvertUtil;
import com.lili.collection.core.utils.JsonPathAnalysisUtil;
import com.lili.collection.core.utils.StrCutUtil;
import com.alibaba.fastjson2.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;


/**
 * 描述: JSON去除原结构扁平化处理器
 *
 * @date: 2023/10/20  15:25
 * @author: lili
 */
@Slf4j
public class JSONFlatProcessor {

    /**
     * 抓取用于取数的节点
     */
    private Set<LinkedNode> fetchNodes;

    /**
     * 当前限制路径下的所有层级
     */
    private Map<String, List<LinkedNode>> levelNodes;

    /**
     * 限制取数的最大路径
     */
    private Set<String> limitPathSet;

    /**
     * key-value 映射配置
     */
    private Map<String, String> configMap;

    /**
     * 抓取数据信息
     */
    private FetchDataInfo fetchInfo;

    /**
     * 不再分析标志
     */
    private boolean noAnalysis = true;

    /**
     * 是否最外层级
     */
    private boolean isOutSingleLevel=true;

    public JSONFlatProcessor() {
        fetchNodes = new LinkedHashSet<>();
        levelNodes = new HashMap<>();
        limitPathSet = new HashSet<>();
    }

    /**
     * 配置映射
     *
     * @param configMap 原始层级名 -> 别名
     */
    public void setConfigMap(Map<String, String> configMap) {
        if (MapUtils.isEmpty(configMap)) {
            throw new IllegalArgumentException("JSON扁平化配置不能为空！");
        }
        this.configMap = configMap;
        this.parseIsSingleLevel();
        for (String levelName : configMap.keySet()) {
            JsonPathAnalysisUtil.appendMaxPath(limitPathSet, JsonPathAnalysisUtil.getParentPath(levelName));
        }
    }

    /**
     * 解析是否最外层
     */
    private void parseIsSingleLevel() {
        for (String key : this.configMap.keySet()) {
            if(JsonPathAnalysisUtil.getLevelNumber(key)>1){
                isOutSingleLevel=false;
                break;
            }
        }
    }

    /**
     * 分析路径结构,移除数组路径结构下的对象路径
     */
    private Set<String> analysisPath(JSONObject jsonObject) {
        if (CollectionUtils.isEmpty(limitPathSet)) {
            return null;
        }
        Set<String> arrayPathSet = new HashSet<>();
        Map<String, List<String>> objPathMap = new HashMap<>();
        for (String limitPath : limitPathSet) {
            if (StringUtils.isBlank(limitPath)) {
                continue;
            }
            if(jsonObject.eval(JSONPath.of("$."+limitPath))==null){
                continue;
            }
            if (StringUtils.isNotBlank(getLastArrayPath(jsonObject, limitPath))) {
                //为数组路径
                arrayPathSet.add(limitPath);
            } else {
                //对象路径
                String head = limitPath.split("\\.")[0];
                List<String> pathList = objPathMap.get(head);
                if (CollectionUtils.isEmpty(pathList)) {
                    pathList = new ArrayList<>();
                    pathList.add(limitPath);
                    objPathMap.put(head, pathList);
                } else {
                    pathList.add(limitPath);
                }
            }
        }
        Set<String> newLimitPathSet = new HashSet<>();
        if (CollectionUtils.isNotEmpty(arrayPathSet) && MapUtils.isNotEmpty(objPathMap)) {
            for (Map.Entry<String, List<String>> entry : objPathMap.entrySet()) {
                String key = entry.getKey();
                for (String arrayPath : arrayPathSet) {
                    if (!arrayPath.startsWith(key)) {
                        newLimitPathSet.addAll(entry.getValue());
                    }
                }
            }
            newLimitPathSet.addAll(arrayPathSet);
        } else if (CollectionUtils.isNotEmpty(arrayPathSet) && MapUtils.isEmpty(objPathMap)) {
            newLimitPathSet.addAll(arrayPathSet);
        } else if (CollectionUtils.isEmpty(arrayPathSet) && MapUtils.isNotEmpty(objPathMap)) {
            getObjPath(jsonObject, objPathMap, newLimitPathSet);
        } else {
            return limitPathSet;
        }
        return newLimitPathSet;
    }

    private void getObjPath(JSONObject jsonObject, Map<String, List<String>> objPathMap, Set<String> newLimitPathSet) {
        Set<String> allObjPaths = JsonPathAnalysisUtil.getAllObjPath(jsonObject);
        Set<String> sameObjPaths=new HashSet<>();
        Set<String> noSameObjPaths=new HashSet<>();
        for (List<String> list : objPathMap.values()) {
            if(CollectionUtils.isNotEmpty(list)){
                for (String objPath : list) {
                    if(allObjPaths.contains(objPath)){
                        sameObjPaths.add(objPath);
                    }else {
                        noSameObjPaths.add(objPath);
                    }
                }
            }
        }
        if(CollectionUtils.isNotEmpty(noSameObjPaths)){
            newLimitPathSet.addAll(noSameObjPaths);
        }
        if(CollectionUtils.isNotEmpty(sameObjPaths)){
            newLimitPathSet.add(sameObjPaths.stream().findFirst().get());
        }
    }

    public String getLastArrayPath(JSONObject jsonObject, String path) {
        if (StringUtils.isBlank(path) || !hasValueByPath(jsonObject, path)) {
            return null;
        }
        int length = JsonPathAnalysisUtil.getLevelNumber(path);
        if (length == 0) {
            return null;
        }
        for (int i = length; i >= 1; i--) {
            String choosePath = JsonPathAnalysisUtil.choosePathByNum(path, i);
            if (isJsonArrayWithPath(jsonObject, choosePath)) {
                return choosePath;
            }
        }
        return null;
    }

    /**
     * 判断json对象对应路径是否是json数组
     *
     * @param jsonObject json对象
     * @param path       json路径
     * @return
     */
    private boolean isJsonArrayWithPath(JSONObject jsonObject, String path) {
        if (jsonObject == null || StringUtils.isBlank(path)) {
            return false;
        }
        String parentPath = JsonPathAnalysisUtil.getParentPath(path);
        String pathField = JsonPathAnalysisUtil.getPathField(path);
        Object pathObj;
        if (StringUtils.isBlank(parentPath)) {
            pathObj = jsonObject.get(pathField);
        } else {
            pathObj = jsonObject.eval(JSONPath.of("$." + parentPath));
        }

        if (pathObj instanceof JSONArray) {
            for (Object obj : ((JSONArray) pathObj)) {
                if (obj instanceof JSONObject) {
                    Object object = (((JSONObject) obj).get(pathField));
                    return (object != null ? (object instanceof JSONArray) : false);
                }
            }
        } else if (pathObj instanceof JSONObject) {
            Object object = (((JSONObject) pathObj).get(pathField));
            return (object != null ? (object instanceof JSONArray) : false);
        }
        return false;
    }

    private boolean hasValueByPath(JSONObject jsonObject, String path) {
        if (jsonObject == null || StringUtils.isBlank(path)) {
            return false;
        }
        return jsonObject.eval(JSONPath.of("$." + path)) != null;
    }

    /**
     * 抓取数配置
     */
    public void setFetchInfo(FetchDataInfo fetchInfo) {
        this.fetchInfo = fetchInfo;
    }


    public void find(LinkedNode parent, String parentName, Object arrayValue, String currentLevelName) {
        JSONObject jsonObj = new JSONObject();
        jsonObj.put("nullKey", arrayValue);
        LinkedNode node = new LinkedNode(parent, parentName, jsonObj);
        String LevelName = (StringUtils.isNotBlank(currentLevelName) ? currentLevelName + "." + parentName : parentName);
        node.setLevelName(LevelName);
        collectNodes(node, LevelName);
        if (isPathLimit(LevelName)) {
            fetchNodes.add(node);
            return;
        }
        if (isIgnore(LevelName)) {
            if (parent != null && isAppendParent(LevelName, parent.getLevelName(), parent)) {
                fetchNodes.add(parent);
            }
        }
    }


    /**
     * 生成下级数据
     *
     * @param parent           父节点
     * @param parentName       父节点名
     * @param data             json对象
     * @param currentLevelName 当前层级名： xx.xx.xx
     */
    public void find(LinkedNode parent, String parentName, JSONObject data, String currentLevelName) {
        LinkedNode node = new LinkedNode(parent, parentName, data);
        String LevelName = (StringUtils.isNotBlank(currentLevelName) ? currentLevelName + "." + parentName : parentName);
        node.setLevelName(LevelName);
        collectNodes(node, LevelName);
        if (isPathLimit(LevelName)) {
            fetchNodes.add(node);
            return;
        }
        if (isIgnore(LevelName)) {
            if (parent != null && isAppendParent(LevelName, parent.getLevelName(), parent)) {
                fetchNodes.add(parent);
            }
            return;
        }
        if (!hasObjectOrArray(data)) {
            fetchNodes.add(node);
        } else {
            for (Map.Entry entry : data.entrySet()) {
                if (entry.getValue() instanceof JSONObject) {
                    find(node, String.valueOf(entry.getKey()), (JSONObject) entry.getValue(), LevelName);
                } else if (entry.getValue() instanceof JSONArray) {
                    find(node, String.valueOf(entry.getKey()), (JSONArray) entry.getValue(), LevelName);
                }
            }
        }
    }

    /**
     * 是否忽略当前层级
     *
     * @param LevelName 层级名
     * @return true:忽略 false:不忽略
     */
    private boolean isIgnore(String LevelName) {
        if (StringUtils.isBlank(LevelName)) {
            return false;
        }
        for (String path : limitPathSet) {
            if (path.startsWith(LevelName)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 该层级路径是否被限制
     *
     * @param LevelName 层级
     * @return true:限制 false:不限制
     */
    private boolean isPathLimit(String LevelName) {
        if (StringUtils.isBlank(LevelName)) {
            return isOutSingleLevel;
        }else {
            for (String path : limitPathSet) {
                if (LevelName.startsWith(path)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 判断是否附加父级节点 node
     *
     * @param currentLevelName 当前层级名
     * @param parentLevelName  父层级名
     * @param parentNode       父节点
     */
    private boolean isAppendParent(String currentLevelName, String parentLevelName, LinkedNode parentNode) {
        if (StringUtils.isBlank(parentLevelName) || parentNode == null) {
            return false;
        }
        Map<String, Object> data = parentNode.getData();
        if (data.get(JsonPathAnalysisUtil.getSuffixPath(currentLevelName, parentLevelName)) != null) {
            for (String limitPath : limitPathSet) {
                if (data.get(JsonPathAnalysisUtil.getSuffixPath(limitPath, parentLevelName)) != null) {
                    return false;
                }
            }
        }
        for (String path : limitPathSet) {
            if (parentLevelName.equals(JsonPathAnalysisUtil.getParentPath(path))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 收集各层级节点数据
     *
     * @param node      节点
     * @param LevelName 层级名
     */
    private void collectNodes(LinkedNode node, String LevelName) {
        if (LevelName != null) {
            List<LinkedNode> linkedNodes = levelNodes.get(LevelName);
            if (CollectionUtils.isEmpty(linkedNodes)) {
                linkedNodes = new ArrayList<>();
                linkedNodes.add(node);
                levelNodes.put(LevelName, linkedNodes);
            } else {
                linkedNodes.add(node);
            }
        }
    }

    /**
     * 解析数组节点
     *
     * @param parent           父节点
     * @param parentName       父节点名
     * @param data             数据
     * @param currentLevelName 当前层级
     */
    public void find(LinkedNode parent, String parentName, JSONArray data, String currentLevelName) {
        for (Object item : data) {
            if (item instanceof JSONObject) {
                if (noAnalysis) {
                    noAnalysis = false;
                    this.limitPathSet = analysisPath((JSONObject) item);
                }
                find(parent, parentName, (JSONObject) item, currentLevelName);
            } else if (item instanceof JSONArray) {
                find(parent, parentName, (JSONArray) item, currentLevelName);
            } else {
                find(parent, parentName, item, currentLevelName);
            }
        }
    }

    /**
     * 判断当前下级是否包含对象或数组
     *
     * @param item k-v对象数据
     */
    protected Boolean hasObjectOrArray(Map<String, Object> item) {
        Object field;
        for (Map.Entry entry : item.entrySet()) {
            field = entry.getValue();
            if (field instanceof Map || isObjectArray(field)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

    /**
     * 判断该对象是否为List类型
     */
    protected Boolean isObjectArray(Object object) {
        return object instanceof List;
    }

    /**
     * 解析原始JSON数据
     *
     * @param parseData json字符串
     */
    public JSONFlatProcessor process(Object parseData) {
        if (ObjectUtil.isEmpty(parseData)) {
            throw new IllegalArgumentException("需要解析的数据不能为空！");
        }
        Object object= (parseData instanceof String) ? getJsonObject((String)parseData) : parseData;
        if (object instanceof JSONArray) {
            find(null, null, (JSONArray) object, null);
        } else if (object instanceof JSONObject) {
            this.limitPathSet = analysisPath((JSONObject) object);
            find(null, null, (JSONObject) object, null);
        } else {
            throw new IllegalArgumentException("解析数据非json格式："+ StrCutUtil.getCutLengthData(String.valueOf(parseData)));
        }
        return this;
    }

    /**
     * 转换为JSON对象
     */
    private Object getJsonObject(String response) {
        try {
            return JSON.parse(response);
        } catch (Exception e) {
            throw new IllegalArgumentException("JSON数据解析错误：{}"+StrCutUtil.getCutLengthData(response), e);
        }
    }

    /**
     * 添加最大扫描路径
     *
     * @param limitPath 扫描路径
     */
    public void addLimitPath(String limitPath) {
        JsonPathAnalysisUtil.appendMaxPath(limitPathSet, limitPath);
    }

    /**
     * 获取所有抓取的节点
     */
    public Set<LinkedNode> getNodes() {
        return fetchNodes;
    }

    /**
     * 获取指定层级路径下的数据
     *
     * @param assignPath 指定层级路径
     */
    public List<Map<String, Object>> getLevelDataByPath(String assignPath) {
        if (CollectionUtils.isEmpty(fetchNodes) || StringUtils.isBlank(assignPath)) {
            return null;
        }
        List<LinkedNode> linkedNodes = levelNodes.get(assignPath);
        if (CollectionUtils.isEmpty(linkedNodes)) {
            return null;
        }
        List<Map<String, Object>> dataList = new ArrayList<>();
        for (LinkedNode node : linkedNodes) {
            Map<String, Object> dataMap = new HashMap<>();
            ;
            Set<String> parsedSet = new HashSet<>();
            collectUpDataWithMap(node, dataMap, parsedSet);
            if (MapUtil.isNotEmpty(dataMap)) {
                dataList.add(dataMap);
            }
        }
        return dataList;
    }

    /**
     * 生成扁平化的数据
     */
    public FetchDataInfo generateFlatData() {
        if (CollectionUtils.isEmpty(fetchNodes)) {
            return null;
        }
        for (LinkedNode node : fetchNodes) {
            Map<String, Object> dataMap = new HashMap<>();
            Set<String> parsedSet = new HashSet<>();
            collectUpDataWithMap(node, dataMap, parsedSet);
            if (MapUtils.isEmpty(dataMap)) {
                continue;
            }
            Map<String, Field> oneRowDataMap = new LinkedHashMap<>();
            boolean isBlankData = true;
            for (Map.Entry<String, String> entry : configMap.entrySet()) {
                Object value = dataMap.get(entry.getKey());
                if (isBlankData && value != null) {
                    isBlankData = false;
                }
                oneRowDataMap.put(entry.getValue(), Field.create(Field.Type.OBJECT, DateConvertUtil.dateTypeHandle(value)));
            }
            if (MapUtils.isEmpty(oneRowDataMap) || isBlankData) {
                continue;
            }
            collectOneRowData(oneRowDataMap);
        }
        return fetchInfo;
    }

    public List<Map<String, Object>> getDataWithMap() {
        if (CollectionUtils.isEmpty(fetchNodes)) {
            return null;
        }
        List<Map<String, Object>> lastDataList = new ArrayList<>();
        for (LinkedNode node : fetchNodes) {
            Map<String, Object> dataMap = new HashMap<>();
            Set<String> parsedSet = new HashSet<>();
            collectUpDataWithMap(node, dataMap, parsedSet);
            if (MapUtils.isEmpty(dataMap)) {
                continue;
            }
            Map<String, Object> oneRowDataMap = new LinkedHashMap<>();
            for (Map.Entry<String, String> entry : configMap.entrySet()) {
                String key =entry.getValue();
                Object value = DateConvertUtil.dateTypeHandle(dataMap.get(entry.getKey()));
                if(StringUtils.isNotBlank(key) && ObjectUtil.isNotEmpty(value)){
                    oneRowDataMap.put(key, value);
                }
            }
            if(MapUtils.isNotEmpty(oneRowDataMap)){
                lastDataList.add(oneRowDataMap);
            }
        }
        return lastDataList;
    }

    /**
     * 收集转换后的数据
     */
    public void collectOneRowData(Map<String, Field> oneRowDataMap) {
        CollectCommonConfig inputConfig = fetchInfo.getModeConfig();
        RecordInfo recordInfo = fetchInfo.getRecordInfo();
        List<Map<String, Field>> dataList = fetchInfo.getDataList();
        if (inputConfig.isIncrementalMode()) {
            Field field = oneRowDataMap.get(inputConfig.getOffsetColumn());
            if (field != null && field.getValue() != null) {
                if (DateConvertUtil.isGreaterThanInitialOffset(inputConfig.getInitialOffset(), String.valueOf(field.getValue()))) {
                    dataList.add(oneRowDataMap);
                    recordInfo.incrementSuccessCount();
                    recordInfo.incrementTotalCount();
                }
            }
        } else {
            dataList.add(oneRowDataMap);
            recordInfo.incrementSuccessCount();
            recordInfo.incrementTotalCount();
        }
    }

    /**
     * 遍历当前节点和所有父节点数据
     *
     * @param node      当前节点
     * @param dataMap   收集的数据Map
     * @param parsedSet 以解析对象的路径
     */
    protected void collectUpDataWithMap(LinkedNode node, Map<String, Object> dataMap, Set<String> parsedSet) {
        parsedSet.add(node.getLevelName());
        for (Map.Entry entry : node.getData().entrySet()) {
            String levelName = StringUtils.isNotBlank(node.getLevelName()) ? node.getLevelName() + "." + entry.getKey() : String.valueOf(entry.getKey());
            if (entry.getValue() instanceof Map) {
                if (parsedSet.contains(levelName)) {
                    if (configMap.containsKey(levelName)) {
                        dataMap.put(levelName, JSON.toJSONString(entry.getValue(), JSONWriter.Feature.WriteMapNullValue));
                    }
                } else {
                    parseObjectData(entry.getValue(), levelName, dataMap, parsedSet);
                }
            } else if (isObjectArray(entry.getValue()) || (entry.getValue() instanceof JSONArray)) {
                if (configMap.containsKey(levelName)) {
                    dataMap.put(levelName, JSON.toJSONString(entry.getValue(),JSONWriter.Feature.WriteMapNullValue));
                }
            } else {
                if (configMap.containsKey(levelName)) {
                    dataMap.put(levelName, entry.getValue());
                }
            }
        }
        if (null != node.getParent()) {
            collectUpDataWithMap(node.getParent(), dataMap, parsedSet);
        }
    }

    /**
     * 解析对象数据
     */
    private void parseObjectData(Object jsonObj, String currentLevel, Map<String, Object> dataMap, Set<String> parsedSet) {
        if (jsonObj instanceof JSONArray) {
            if (configMap.containsKey(currentLevel)) {
                dataMap.put(currentLevel, JSON.toJSONString(jsonObj,JSONWriter.Feature.WriteMapNullValue));
            }
        } else if (jsonObj instanceof JSONObject) {
            //行数据
            JSONObject jsonObject = (JSONObject) jsonObj;
            if (configMap.containsKey(currentLevel)) {
                dataMap.put(currentLevel, JSON.toJSONString(jsonObject,JSONWriter.Feature.WriteMapNullValue));
            }
            Set<String> keySet = jsonObject.keySet();
            for (String key : keySet) {
                Object objValue = jsonObject.get(key);
                String nextLevel = (StringUtils.isNotBlank(currentLevel) ? currentLevel + "." + key : key);
                if (parsedSet.contains(nextLevel)) {
                    continue;
                }
                if (objValue instanceof JSONObject) {
                    parseObjectData(objValue, nextLevel, dataMap, parsedSet);
                } else if (objValue instanceof JSONArray) {
                    if (configMap.containsKey(nextLevel)) {
                        dataMap.put(nextLevel, JSON.toJSONString(objValue,JSONWriter.Feature.WriteMapNullValue));
                    }
                } else {
                    if (configMap.containsKey(nextLevel)) {
                        dataMap.put(nextLevel, objValue);
                    }
                }
            }
        }
    }
}